"""
闲鱼IM聊天管理器 - 基于WebSocket直连
参考: XianyuAutoAgent
"""
import asyncio
import json
import re
from datetime import datetime
from typing import Dict, Optional, Tuple
from loguru import logger

from core.command_manager import CommandRes
from modules.xianyu_im_client import XianyuIMClient


class ChatManagerWS:
    """闲鱼聊天管理器（基于WebSocket直连）"""
    
    def __init__(self, browser_manager, state_manager):
        self.browser_manager = browser_manager
        self.state = state_manager
        self.tcp_client = None
        self.im_client: Optional[XianyuIMClient] = None
        self.im_task = None
        self._running = False
        
        # ✅ 会话管理：存储 seller_id + item_id -> chat_id 的映射
        # 格式：{(seller_id, item_id): chat_id}
        self._chat_id_map: Dict[Tuple[str, str], str] = {}
        # ✅ 反向映射：存储 chat_id -> (seller_id, item_id) 的映射
        # 格式：{chat_id: (seller_id, item_id)}
        self._reverse_chat_id_map: Dict[str, Tuple[str, str]] = {}
    
    def set_tcp_client(self, tcp_client):
        """设置TCP客户端引用"""
        self.tcp_client = tcp_client
    
    def _get_seller_id_by_chat_id(self, chat_id: str) -> Optional[str]:
        """
        通过chat_id获取seller_id
        
        Args:
            chat_id: 会话ID
            
        Returns:
            seller_id或None
        """
        if not chat_id:
            return None
        
        # 从反向映射中获取(seller_id, item_id)
        mapping = self._reverse_chat_id_map.get(chat_id)
        if mapping:
            seller_id, _ = mapping
            return seller_id
        
        return None
    
    async def start_im_connection(self):
        """启动IM WebSocket连接"""
        try:
            # 获取Cookie字典
            cookies_dict = await self.browser_manager.get_cookie_dict()
            if not cookies_dict or 'unb' not in cookies_dict:
                logger.warning("[Chat] 无法启动IM：缺少用户ID (unb)")
                return False
            
            # 创建IM客户端（✅ 传递browser_manager用于风控处理）
            self.im_client = XianyuIMClient(cookies_dict, self.state, self.browser_manager)
            
            # ✅ 传递TCP客户端引用（用于上报消息）
            self.im_client.tcp_client = self.tcp_client
            
            # 设置消息回调
            self.im_client.set_message_callback(self._on_im_message)
            
            # ✅ 设置会话创建回调，用于更新chat_id映射
            async def on_conversation_created(seller_id: str, item_id: Optional[str], chat_id: str):
                key = (seller_id, item_id or '')
                self._chat_id_map[key] = chat_id
                logger.info(f"[Chat] ✅ 会话已创建并更新映射: seller_id={seller_id}, item_id={item_id}, chat_id={chat_id}")
            
            self.im_client.conversation_created_callback = on_conversation_created
            
            # 启动连接任务
            self._running = True
            self.im_task = asyncio.create_task(self.im_client.connect())
            
            # ✅ 启动会话同步任务
            asyncio.create_task(self._start_conversation_sync_task())
            
            logger.info("[Chat] IM WebSocket连接已启动")
            return True
            
        except Exception as e:
            logger.error(f"[Chat] 启动IM连接失败: {e}")
            return False
    
    async def _on_im_message(self, message_data: dict):
        """处理收到的IM消息"""
        try:
            # ✅ 检查是否是批量历史消息事件
            event = message_data.get('event')
            if event == 'batch_history_messages':
                # 批量历史消息，添加client_id后转发
                client_id = self.state.get('client_id')
                message_data['client_id'] = client_id
                
                # 🔍 调试：打印发送的数据
                logger.info(f"[Chat] 🔍 准备发送批量消息: keys={list(message_data.keys())}")
                logger.info(f"[Chat] 🔍 messages类型: {type(message_data.get('messages'))}")
                logger.info(f"[Chat] 🔍 messages长度: {len(message_data.get('messages', []))}")
                logger.info(f"[Chat] 🔍 count字段: {message_data.get('count')}")
                
                await self.tcp_client.send(message_data)
                logger.info(f"[Chat] ✅ 已转发批量历史消息: {message_data.get('count', 0)}条")
                return
            
            chat_id = message_data.get('chat_id')
            sender_id = message_data.get('sender_id')
            sender_name = message_data.get('sender_name')
            item_id = message_data.get('item_id')
            message_content = message_data.get('message')
            create_time = message_data.get('create_time')
            message_id = message_data.get('message_id')  # ✅ 提取真实的消息ID（格式为 "3802712875012.PNM"）
            message_type = message_data.get('message_type', 'text')  # ✅ 消息类型（text/image/voice/recall）
            image_url = message_data.get('image_url')  # ✅ 图片URL（如果是图片消息）
            image_width = message_data.get('image_width')  # ✅ 图片宽度
            image_height = message_data.get('image_height')  # ✅ 图片高度
            voice_url = message_data.get('voice_url')  # ✅ 语音URL（如果是语音消息）
            voice_duration = message_data.get('voice_duration')  # ✅ 语音时长（秒）
            voice_size_bytes = message_data.get('voice_size_bytes')  # ✅ 语音大小（字节）
            is_recall = message_data.get('is_recall', False)  # ✅ 是否是撤回消息
            recall_message_id = message_data.get('recall_message_id')  # ✅ 被撤回的消息ID
            
            # ✅ 安全处理时间戳（移到前面，确保所有分支都能使用）
            message_time_iso = None
            if create_time:
                try:
                    message_time_iso = datetime.fromtimestamp(create_time / 1000).isoformat()
                except (ValueError, TypeError, OSError) as e:
                    logger.warning(f"[Chat] ⚠️ 无法解析create_time: {create_time}, 错误: {e}")
                    message_time_iso = datetime.now().isoformat()
            else:
                message_time_iso = datetime.now().isoformat()
            
            # ✅ 更新chat_id映射（从接收到的消息中学习chat_id）
            # sender_id是发送者的ID（不是接收者的ID）
            # 如果sender_id不是我们自己的ID，说明这是别人发送给我们的消息（接收消息）
            # 直接使用sender_id作为seller_id，并更新chat_id映射
            myid = self.im_client.myid if self.im_client else None
            
            # ✅ 如果sender_id不是我们自己的ID，说明这是别人发送给我们的消息（接收消息）
            # 直接使用sender_id作为seller_id，并更新chat_id映射
            if chat_id and sender_id and myid and str(sender_id) != str(myid):
                # ✅ sender_id就是发送者的ID，直接使用作为seller_id
                key = (sender_id, item_id or '')
                if key not in self._chat_id_map or self._chat_id_map[key] != chat_id:
                    self._chat_id_map[key] = chat_id
                    self._reverse_chat_id_map[chat_id] = key  # ✅ 同时更新反向映射
                    logger.info(f"[Chat] 📝 已更新chat_id映射: seller_id={sender_id}, item_id={item_id}, chat_id={chat_id}")
            elif chat_id and sender_id and myid and str(sender_id) == str(myid):
                # ✅ 如果sender_id是我们自己的ID，说明这是我们发送的消息（但这种情况不应该出现在接收消息中）
                # 跳过更新映射，因为发送的消息已经在send_message时更新了映射
                logger.debug(f"[Chat] 跳过自己发送的消息的映射更新: sender_id={sender_id}, myid={myid}")
            
            reply_message_id = message_data.get('reply_message_id')  # ✅ 提取回复消息ID
            system_data = message_data.get('system_data')  # ✅ 提取系统消息数据（textCard）
            
            # ✅ 根据消息类型输出不同的日志
            if is_recall:
                logger.info(f"[Chat] 🔄 收到撤回消息: {sender_name} ({sender_id}), 被撤回的消息ID={recall_message_id}")
            elif message_type == "image":
                logger.info(f"[Chat] 📷 收到图片消息: {sender_name} ({sender_id}), image_url={image_url}, size={image_width}x{image_height}, message_id={message_id}")
                # ✅ 如果图片消息没有URL，打印详细的调试信息
                if not image_url:
                    logger.error(f"[Chat] ❌ 图片消息缺少image_url！message_id={message_id}, message_data完整内容: {json.dumps(message_data, ensure_ascii=False, indent=2)[:2000]}")
            elif message_type == "voice":
                logger.info(f"[Chat] 🎤 收到语音消息: {sender_name} ({sender_id}), voice_url={voice_url}, duration={voice_duration}s, message_id={message_id}")
                # ✅ 如果语音消息没有URL，打印详细的调试信息
                if not voice_url:
                    logger.error(f"[Chat] ❌ 语音消息缺少voice_url！message_id={message_id}, message_data完整内容: {json.dumps(message_data, ensure_ascii=False, indent=2)[:2000]}")
            elif message_type == "system":
                logger.info(f"[Chat] 🔔 收到系统消息: {sender_name} ({sender_id}), message_id={message_id}, system_data={system_data}")
            else:
                logger.info(f"[Chat] ✅ 收到新消息: {sender_name} ({sender_id}): {message_content} (message_id={message_id}, reply_message_id={reply_message_id})")
            
            # 推送到控制端
            if self.tcp_client:
                client_id = self.state.get('client_id')
                
                # ✅ 判断消息方向：
                # sender_id是发送者的ID（不是接收者的ID）
                # 如果sender_id不是我们自己的ID，说明这是别人发送给我们的消息（接收消息）
                # 如果sender_id是我们自己的ID，说明这是我们发送的消息（但这种情况不应该出现在接收消息中，因为发送的消息会在send_message时推送）
                myid = self.im_client.myid if self.im_client else None
                is_sent_message = (myid and sender_id and str(sender_id) == str(myid))
                
                # ✅ 添加详细日志，跟踪消息方向判断
                logger.info(f"[Chat] 📊 消息方向判断: sender_id={sender_id}, myid={myid}, is_sent_message={is_sent_message}, message_type={message_type}, message_id={message_id}")
                
                # ========== 消息状态机：检查是否是官方响应 ==========
                temp_id = message_data.get('temp_id')
                mid = message_data.get('mid')
                
                # ✅ 判断消息方向并设置seller_id
                if is_sent_message:
                    # ✅ 我们发送的消息（官方响应）
                    # ⚠️ 重要：不要跳过！前端需要这个真实消息ID来替换临时消息
                    # seller_id应该是接收者的ID（对方的ID），使用to_user_id
                    direction = 'send'
                    # ✅ 使用to_user_id作为seller_id（接收者的ID）
                    to_user_id = message_data.get("to_user_id")
                    if to_user_id:
                        seller_id = to_user_id
                        seller_name = message_data.get("seller_name", "")  # ✅ 从message_data获取seller_name
                        logger.info(f"[Chat] 📤 官方响应（真实消息ID）: message_id={message_id}, seller_id={seller_id} (from to_user_id), direction={direction}, temp_id={temp_id}, mid={mid}")
                    else:
                        # 如果没有to_user_id，从chat_id映射获取
                        seller_id = self._get_seller_id_by_chat_id(chat_id) if chat_id else None
                        seller_name = message_data.get("seller_name", "")  # ✅ 从message_data获取seller_name
                        if not seller_id:
                            # 如果映射中也找不到，记录错误并跳过这条消息（避免创建错误会话）
                            logger.error(f"[Chat] ❌ 无法确定seller_id: chat_id={chat_id}, to_user_id={to_user_id}, 跳过推送")
                            return
                        logger.info(f"[Chat] 📤 官方响应（真实消息ID）: message_id={message_id}, seller_id={seller_id} (from chat_id mapping), direction={direction}, temp_id={temp_id}, mid={mid}")
                    
                    # ========== 如果有 temp_id 和 mid，说明是消息状态机流程，推送 message_confirmed 事件 ==========
                    if temp_id and mid:
                        logger.info(f"[Chat] ✅ 检测到消息状态机流程，推送 message_confirmed 事件: temp_id={temp_id}, mid={mid}, real_message_id={message_id}")
                        
                        confirmed_payload = {
                            "temp_id": temp_id,
                            "mid": mid,
                            "message_id": message_id,  # 真实的消息ID
                            "message_status": 2,  # SENT - 已发送
                            "conversation_id": chat_id,
                            "chat_id": chat_id,
                            "seller_id": seller_id,
                            "item_id": item_id,
                            "client_id": client_id,
                            "direction": "send",
                            "message_type": message_type,
                            "message_content": message_content,
                            "message_time": message_time_iso,
                            # ✅ 图片信息（如果有）
                            "image_url": message_data.get('image_url'),
                            "image_width": message_data.get('image_width'),
                            "image_height": message_data.get('image_height'),
                        }
                        
                        # 推送 message_confirmed 事件
                        await self.tcp_client.send(CommandRes.MessageEvent().Success(
                            data={
                                "event": "message_confirmed",
                                "client_id": client_id,
                                "data": confirmed_payload
                            }
                        ))
                        logger.info(f"[Chat] ✅ message_confirmed 事件已推送: temp_id={temp_id}, mid={mid}, real_message_id={message_id}")
                        
                        # 推送完成后直接返回，不再推送 chat_message 事件
                        return
                
                else:
                    # ✅ 接收消息，直接使用sender_id作为seller_id
                    seller_id = sender_id
                    seller_name = sender_name
                    direction = 'recv'
                
                # ✅ 如果sender_id不是我们自己的ID，说明这是别人发送给我们的消息（接收消息）
                # 直接使用sender_id作为seller_id，这是正确的
                # 之前的错误逻辑：认为sender_id是接收者的ID，需要通过chat_id映射反推，这是错误的
                # 实际上sender_id就是发送者的ID，直接使用即可
                
                payload = {
                    "client_id": client_id,
                    "seller_id": seller_id,
                    "seller_name": seller_name,
                    "item_id": item_id,
                    "chat_id": chat_id,  # ✅ 传递chat_id，用于构建conversation_id
                    "message_id": message_id or str(create_time) if create_time else None,  # ✅ 使用真实的消息ID
                    "message_type": message_type,  # ✅ 传递消息类型（text/image/voice/recall）
                    "direction": direction,
                    "message_content": message_content,
                    "message_time": message_time_iso,
                    # ✅ 官方API字段（如果有）
                    "create_at": message_data.get("create_at"),
                    "content_type": message_data.get("content_type"),
                    "msg_read_status_setting": message_data.get("msg_read_status_setting"),
                    "receiver_count": message_data.get("receiver_count"),
                    "unread_count": message_data.get("unread_count"),
                    "uuid": message_data.get("uuid"),
                }
                
                # ✅ 如果存在回复消息ID，添加到payload中（后续可存储到message_extra JSON字段）
                if reply_message_id:
                    payload["reply_message_id"] = reply_message_id
                
                # ✅ 如果是图片消息，添加图片信息（即使URL为空也要添加，让后端知道这是图片消息）
                if message_type == "image":
                    # ✅ 无论是否有URL，都添加image_url字段（可能为None）
                    payload["image_url"] = image_url  # 即使为None也添加
                    if image_width:
                        payload["image_width"] = image_width
                    if image_height:
                        payload["image_height"] = image_height
                    # ✅ 调试日志：如果消息类型是图片但没有image_url，记录错误
                    if not image_url:
                        logger.error(f"[Chat] ❌ 图片消息缺少image_url: message_id={message_id}, payload={json.dumps(payload, ensure_ascii=False)[:500]}")
                
                # ✅ 如果是语音消息，添加语音信息（即使URL为空也要添加，让后端知道这是语音消息）
                if message_type == "voice":
                    # ✅ 无论是否有URL，都添加voice_url字段（可能为None）
                    payload["voice_url"] = voice_url  # 即使为None也添加
                    if voice_duration is not None:
                        payload["voice_duration"] = voice_duration
                    if voice_size_bytes is not None:
                        payload["voice_size_bytes"] = voice_size_bytes
                    # ✅ 调试日志：如果消息类型是语音但没有voice_url，记录错误
                    if not voice_url:
                        logger.error(f"[Chat] ❌ 语音消息缺少voice_url: message_id={message_id}, payload={json.dumps(payload, ensure_ascii=False)[:500]}")
                
                # ✅ 如果是撤回消息，添加撤回相关信息
                if is_recall:
                    payload["is_recall"] = True
                    if recall_message_id:
                        payload["recall_message_id"] = recall_message_id
                
                # ✅ 如果是系统消息（textCard），添加系统消息数据到message_extra
                if message_type == "system" and system_data:
                    # 处理系统消息中的HTML标签
                    try:
                        if isinstance(system_data, dict):
                            # 定义去除HTML标签的函数
                            def clean_html(html_text):
                                if not html_text:
                                    return ""
                                return re.sub(r'<[^>]+>', '', html_text).strip()

                            # 获取原始数据
                            raw_title = system_data.get("title", "")
                            raw_content = system_data.get("content", "")
                            
                            # 清洗数据
                            clean_title = clean_html(raw_title)
                            clean_content = clean_html(raw_content)
                            
                            # 更新system_data中的字段（或者添加新字段供前端使用）
                            # 这里直接更新，确保前端拿到的是干净数据
                            system_data["title"] = clean_title
                            system_data["content"] = clean_content
                            
                            # 同时也保留原始数据，以防万一
                            system_data["raw_title"] = raw_title
                            system_data["raw_content"] = raw_content

                            # 构建显示的纯文本消息内容
                            # 格式：标题 + 换行 + 内容
                            display_content = ""
                            if clean_title:
                                display_content += f"【{clean_title}】"
                            if clean_content:
                                display_content += f"\n{clean_content}"
                            
                            if display_content:
                                payload["message_content"] = display_content.strip()
                                # 同时更新message_content变量，以便日志打印
                                message_content = payload["message_content"]
                            else:
                                # 如果没有解析出内容，回退到使用message_content
                                pass
                                
                    except Exception as e:
                        logger.error(f"[Chat] 解析系统消息HTML失败: {e}")
                    
                    payload["system_data"] = system_data
                
                await self.tcp_client.send(CommandRes.ChatMessage().Success(data=payload))
                logger.debug(f"[Chat] 消息已推送到控制端: message_id={message_id}, message_type={message_type}, reply_message_id={reply_message_id}")
                
        except Exception as e:
            logger.error(f"[Chat] 处理IM消息失败: {e}")
    
    async def open_chat(self, seller_id: str, item_id: Optional[str] = None, seller_name: Optional[str] = None):
        """
        打开聊天（创建会话并确保IM连接）
        
        ⚠️ 注意：商品详情和卖家信息已由控制端通过匿名请求获取，避免Agent风控
        Agent只负责IM连接和会话创建，不再查询商品信息
        """
        try:
            # 如果IM未连接，先启动
            if not self.im_client or not self.im_client.connected:
                await self.start_im_connection()
                # 等待连接建立
                await asyncio.sleep(2)
            
            # ✅ 尝试创建会话（如果还没有会话）
            key = (seller_id, item_id or '')
            chat_id = None  # ✅ 初始化chat_id变量
            if key not in self._chat_id_map:
                # ✅ 精简日志：创建会话不记录INFO级别日志
                try:
                    # ✅ 调用创建会话接口
                    await self.im_client.create_conversation(seller_id, item_id)
                    # ✅ 等待服务器响应（会话创建响应会通过回调更新_chat_id_map）
                    # 等待时间：最多等待2秒，让服务器有时间响应
                    await asyncio.sleep(2)
                    
                    # ✅ 检查是否已通过回调更新了chat_id
                    if key in self._chat_id_map:
                        chat_id = self._chat_id_map[key]
                        # ✅ 精简日志：会话创建成功不记录INFO级别日志
                    else:
                        logger.warning(f"[Chat] ⚠️ 会话创建请求已发送，但尚未收到响应（chat_id将在响应中获取）")
                except Exception as e:
                    logger.warning(f"[Chat] ⚠️ 创建会话失败: {e}，将尝试在首次发送消息时创建")
            
            # ✅ 不再查询商品详情，因为控制端已经通过匿名请求获取了
            # 只返回传入的参数，确保seller_id是正确的纯数字ID
            # ✅ 精简日志：打开聊天不记录日志
            
            # 构建返回数据（只返回基本信息，商品信息由控制端提供）
            result = {
                "seller_id": seller_id,  # 使用传入的seller_id（应该是纯数字ID）
                "item_id": item_id,
                "seller_name": seller_name,
                "chat_id": chat_id  # ✅ 返回chat_id（如果有的话）
            }
            
            logger.debug(f"[Chat] open_chat返回结果: seller_id={seller_id}, item_id={item_id}, chat_id={chat_id}")
            
            return result
            
        except Exception as e:
            logger.error(f"[Chat] 打开聊天失败: {e}")
            raise
    
    async def send_message(self, seller_id: str, item_id: Optional[str], message: str, 
                          temp_id: Optional[str] = None, mid: Optional[str] = None):
        """
        发送消息（通过WebSocket）+ 消息状态机支持
        
        Args:
            seller_id: 对方用户ID
            item_id: 商品ID（可选）
            message: 消息内容
            temp_id: 前端生成的临时ID（用于乐观更新）
            mid: 消息mid（用于匹配官方响应）
        
        注意：
        - chat_id（会话ID）在闲鱼中是由服务器分配的
        - 如果还没有会话，可以使用seller_id作为临时chat_id，服务器会自动创建会话
        - 如果已经有会话，应该使用从接收消息中获取的chat_id
        - 这里暂时使用seller_id作为chat_id，服务器会处理会话创建
        """
        try:
            if not self.im_client or not self.im_client.connected:
                raise Exception("IM未连接，无法发送消息")
            
            # ✅ chat_id生成策略：
            # 1. 优先使用已存储的chat_id（从接收消息中学习到的）
            # 2. 如果还没有会话，尝试使用item_id+seller_id的组合作为临时chat_id
            # 3. 如果还没有item_id，使用seller_id作为临时chat_id
            # 注意：根据闲鱼的行为，首次发送消息时，服务器应该能自动创建会话
            key = (seller_id, item_id or '')
            chat_id = self._chat_id_map.get(key)
            
            if not chat_id:
                # ✅ 首次发送消息前，尝试先创建会话
                logger.info(f"[Chat] 首次发送消息，尝试先创建会话: seller_id={seller_id}, item_id={item_id}")
                try:
                    await self.im_client.create_conversation(seller_id, item_id)
                    # 等待服务器响应（最多2秒）
                    await asyncio.sleep(2)
                    
                    # 检查是否已通过回调更新了chat_id
                    if key in self._chat_id_map:
                        chat_id = self._chat_id_map[key]
                        logger.info(f"[Chat] ✅ 会话已创建，使用chat_id: {chat_id}")
                    else:
                        # 如果还没有chat_id，使用seller_id作为临时chat_id
                        chat_id = seller_id
                        logger.warning(f"[Chat] ⚠️ 会话创建请求已发送但尚未收到响应，使用seller_id作为临时chat_id: {seller_id}")
                except Exception as e:
                    logger.warning(f"[Chat] ⚠️ 创建会话失败: {e}，使用seller_id作为临时chat_id")
                    chat_id = seller_id
            else:
                logger.info(f"[Chat] 使用已存储的chat_id: {chat_id}（从历史消息中学习）")
            
            # ========== 消息状态机：生成 temp_id 和 mid ==========
            # 如果前端没有提供，则Agent生成
            if not temp_id:
                temp_id = f"agent-{int(datetime.now().timestamp() * 1000)}-{id(message)}"
            if not mid:
                mid = f"{int(datetime.now().timestamp() * 1000)}{int(datetime.now().timestamp() * 1000) % 1000} 0"
            
            logger.info(f"[Chat] 📤 准备发送消息: seller_id={seller_id}, item_id={item_id}, chat_id={chat_id}, temp_id={temp_id}, mid={mid}")
            
            # ========== 第1步：立即推送 message_created 事件 ==========
            # 前端需要这个事件来：
            # 1. 确认消息已被Agent接收
            # 2. 获取临时message_id（用于在列表中显示）
            # 3. 关联temp_id和mid
            if self.tcp_client:
                client_id = self.state.get('client_id')
                temp_message_id = f"{temp_id}.TMP"  # 临时消息ID
                
                created_payload = {
                    "temp_id": temp_id,
                    "mid": mid,
                    "message_id": temp_message_id,
                    "message_status": 1,  # PENDING - 等待官方响应
                    "conversation_id": chat_id,
                    "chat_id": chat_id,
                    "seller_id": seller_id,
                    "item_id": item_id,
                    "client_id": client_id,
                    "direction": "send",
                    "message_type": "text",
                    "message_content": message,
                    "message_time": datetime.now().isoformat()
                }
                
                # 推送 message_created 事件（用于数据库保存和前端更新）
                await self.tcp_client.send(CommandRes.MessageEvent().Success(
                    data={
                        "event": "message_created",
                        "client_id": client_id,
                        "data": created_payload
                    }
                ))
                logger.info(f"[Chat] ✅ message_created 事件已推送: temp_id={temp_id}, mid={mid}")
            
            # ========== 第2步：发送消息到闲鱼官方 ==========
            try:
                # 传递 temp_id 和 mid 到 im_client，用于后续匹配官方响应
                msg_info = await self.im_client.send_message(
                    chat_id, seller_id, message, 
                    item_id=item_id, 
                    temp_id=temp_id,  # ← 新增
                    mid=mid  # ← 新增
                )
                # msg_info 格式: (mid, msg_uuid, chat_id, to_user_id, text, item_id, message_id)
                # 注意：真实的message_id会在官方响应中返回
                logger.info(f"[Chat] ✅ 消息已发送到闲鱼: {message[:50]}")
                
            except Exception as e:
                # ========== 发送失败：推送 message_failed 事件 ==========
                error_msg = str(e)
                logger.error(f"[Chat] ❌ 消息发送失败: {e}")
                
                if self.tcp_client:
                    client_id = self.state.get('client_id')
                    failed_payload = {
                        "temp_id": temp_id,
                        "mid": mid,
                        "message_status": 6,  # FAILED
                        "error": error_msg,
                        "conversation_id": chat_id,
                        "seller_id": seller_id,
                        "client_id": client_id
                    }
                    
                    await self.tcp_client.send(CommandRes.MessageEvent().Success(
                        data={
                            "event": "message_failed",
                            "client_id": client_id,
                            "data": failed_payload
                        }
                    ))
                    logger.info(f"[Chat] ❌ message_failed 事件已推送: temp_id={temp_id}, error={error_msg}")
                
                # 不抛出异常，让上层决定如何处理
                if "conversation not exist" in error_msg.lower():
                    logger.error(f"[Chat] 建议：确保seller_id和item_id正确，且已调用open_chat创建会话")
                else:
                    raise
            
            return {
                "seller_id": seller_id,
                "item_id": item_id,
                "message": message
            }
            
        except Exception as e:
            logger.error(f"[Chat] 发送消息失败: {e}")
            raise
    
    async def send_image_message(self, seller_id: str, item_id: Optional[str], image_url: str, 
                                image_width: int = None, image_height: int = None,
                                temp_id: Optional[str] = None, mid: Optional[str] = None):
        """
        发送图片消息（通过WebSocket）+ 消息状态机支持
        
        Args:
            seller_id: 卖家ID（纯数字）
            item_id: 商品ID（可选）
            image_url: 图片URL（从上传API获取）
            image_width: 图片宽度（可选）
            image_height: 图片高度（可选）
            temp_id: 前端生成的临时ID（用于乐观更新）
            mid: 消息mid（用于匹配官方响应）
        """
        try:
            if not self.im_client or not self.im_client.connected:
                raise Exception("IM未连接，无法发送图片")
            
            # ✅ 使用与send_message相同的chat_id策略
            key = (seller_id, item_id or '')
            chat_id = self._chat_id_map.get(key)
            
            if not chat_id:
                # ✅ 首次发送消息前，尝试先创建会话
                logger.info(f"[Chat] 首次发送图片，尝试先创建会话: seller_id={seller_id}, item_id={item_id}")
                try:
                    await self.im_client.create_conversation(seller_id, item_id)
                    
                    # ✅ 等待会话创建响应，最多等待5秒，每0.5秒检查一次
                    for i in range(10):  # 5秒 / 0.5秒 = 10次
                        await asyncio.sleep(0.5)
                        if key in self._chat_id_map:
                            chat_id = self._chat_id_map[key]
                            logger.info(f"[Chat] ✅ 会话已创建，使用chat_id: {chat_id}")
                            break
                    else:
                        chat_id = seller_id
                        logger.warning(f"[Chat] ⚠️ 会话创建请求已发送但5秒内未收到响应，使用seller_id作为临时chat_id: {seller_id}")
                except Exception as e:
                    logger.warning(f"[Chat] ⚠️ 创建会话失败: {e}，使用seller_id作为临时chat_id")
                    chat_id = seller_id
            
            logger.info(f"[Chat] 📷 准备发送图片: seller_id={seller_id}, item_id={item_id}, chat_id={chat_id}, url={image_url}")
            
            # ✅ 发送图片消息
            try:
                msg_info = await self.im_client.send_image_message(chat_id, seller_id, image_url, image_width, image_height, item_id=item_id, temp_id=temp_id, mid=mid)
                logger.info(f"[Chat] ✅ 图片已发送: {image_url}")
            except Exception as e:
                logger.error(f"[Chat] ❌ 图片发送失败: {e}")
                raise
            
            # 发送成功后，推送消息到控制端
            if self.tcp_client:
                from datetime import datetime
                client_id = self.state.get('client_id')
                temp_message_id = f"{int(datetime.now().timestamp() * 1000)}.TMP"
                
                payload = {
                    "client_id": client_id,
                    "seller_id": seller_id,
                    "seller_name": None,
                    "item_id": item_id,
                    "chat_id": chat_id,  # ✅ 传递chat_id，用于构建conversation_id
                    "message_id": temp_message_id,
                    "message_type": 'image',
                    "direction": 'send',
                    "message_content": '[图片]',
                    "image_url": image_url,
                    "image_width": image_width,
                    "image_height": image_height,
                    "message_time": datetime.now().isoformat()
                }
                
                await self.tcp_client.send(CommandRes.ChatMessage().Success(data=payload))
                logger.debug(f"[Chat] 图片消息已推送到控制端: {image_url}")
            
        except Exception as e:
            logger.error(f"[Chat] 发送图片消息失败: {e}")
            raise
    
    async def mark_read(self, seller_id: str, item_id: Optional[str], message_ids: list):
        """
        标记消息为已读（发送已读回执）
        
        Args:
            seller_id: 卖家ID
            item_id: 商品ID（可选）
            message_ids: 消息ID列表，格式为 ["3808247424509.PNM", ...]
        """
        try:
            if not self.im_client or not self.im_client.connected:
                logger.warning("[Chat] IM未连接，无法发送已读回执")
                return CommandRes.ChatRead().Fail(msg="IM未连接")
            
            # ✅ 发送已读回执
            success = await self.im_client.mark_messages_read(message_ids)
            if success:
                logger.debug(f"[Chat] ✅ 已读回执已发送: seller_id={seller_id}, message_count={len(message_ids)}")
                return CommandRes.ChatRead().Success(data={"seller_id": seller_id, "item_id": item_id, "message_count": len(message_ids)})
            else:
                return CommandRes.ChatRead().Fail(msg="发送已读回执失败")
        except Exception as e:
            logger.error(f"[Chat] 发送已读回执失败: {e}")
            return CommandRes.ChatRead().Fail(msg=str(e))
    
    async def recall_message(self, seller_id: str, item_id: Optional[str], message_id: str):
        """
        撤回消息
        
        Args:
            seller_id: 卖家ID
            item_id: 商品ID（可选）
            message_id: 要撤回的消息ID，格式为 "3808247424509.PNM"
        """
        try:
            if not self.im_client or not self.im_client.connected:
                logger.warning("[Chat] IM未连接，无法撤回消息")
                return CommandRes.ChatRecall().Fail(msg="IM未连接")
            
            if not message_id:
                logger.warning("[Chat] 撤回消息失败：消息ID为空")
                return CommandRes.ChatRecall().Fail(msg="消息ID不能为空")
            
            # ✅ 获取chat_id（从chat_id_map中查找）
            key = (seller_id, item_id or '')
            chat_id = self._chat_id_map.get(key)
            
            if not chat_id:
                # 如果没有chat_id，尝试使用seller_id作为临时chat_id
                chat_id = seller_id
                logger.warning(f"[Chat] ⚠️ 未找到chat_id映射，使用seller_id作为临时chat_id: {seller_id}")
            
            # ✅ 发送撤回消息请求
            success = await self.im_client.recall_message(message_id, chat_id)
            if success:
                logger.info(f"[Chat] ✅ 撤回消息请求已发送: seller_id={seller_id}, message_id={message_id}")
                return CommandRes.ChatRecall().Success(data={"seller_id": seller_id, "item_id": item_id, "message_id": message_id})
            else:
                return CommandRes.ChatRecall().Fail(msg="发送撤回消息请求失败")
        except Exception as e:
            logger.error(f"[Chat] 撤回消息失败: {e}")
            return CommandRes.ChatRecall().Fail(msg=str(e))
    
    async def _start_conversation_sync_task(self):
        """启动会话同步任务"""
        try:
            logger.info("[Chat] 启动会话同步任务")
            
            # 等待IM客户端连接
            while not self.im_client or not self.im_client.connected:
                if not self._running:
                    return
                await asyncio.sleep(5)
            
            # 首次同步（延迟30秒，等待登录完成）
            await asyncio.sleep(30)
            
            if self.im_client and self.im_client.connected:
                logger.info("[Chat] 开始首次会话同步（仅启动时同步一次）")
                await self.im_client.sync_conversations_to_backend()
            
            # ✅ 已禁用定期同步：只在启动时同步一次，避免占用资源影响抢商品
            logger.info("[Chat] 会话同步任务完成（不再定期同步，专注于实时消息和抢商品）")
        except Exception as e:
            logger.error(f"[Chat] 会话同步任务失败: {e}")
    
    async def shutdown(self):
        """关闭聊天管理器"""
        self._running = False
        
        if self.im_client:
            await self.im_client.disconnect()
        
        if self.im_task and not self.im_task.done():
            self.im_task.cancel()
            try:
                await self.im_task
            except asyncio.CancelledError:
                pass
        
        logger.info("[Chat] 聊天管理器已关闭")

